package rlog

import (
	"context"
	"fmt"
	"io"
	"os"
	"path"
	"runtime"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"gitee.com/ruige_fun/util/rwheel"

	"github.com/logrusorgru/aurora/v4"
)

const (
	LogFileModel          = 0660
	DefaultLogFileMaxSize = 1024 * 1024 * 10
)

const (
	tagDebug    = "debug "
	tagInfo     = "info  "
	tagSuccess  = "succ  "
	tagWarn     = "warn  "
	tagError    = "error "
	tagPanic    = "panic "
	tagExit     = "exit  "
	tagPrint    = "print "
	tagMust     = "must  "
	tagMustOnly = "only  "
)

var uniqueID = time.Now().UnixNano()

// NewLogger 新建一个日志管理器
// windows彩色日志：import "github.com/mattn/go-colorable" 参数传 colorable.NewColorableStdout()
func NewLogger(stdout io.Writer) RLogger {
	l := &_logger{
		_runtimeCaller:      2,
		_consoleWriter:      stdout,
		_consoleLevel:       LevelDebug,
		_fileLevel:          LevelDebug,
		_curFileSize:        0,
		_maxFileSize:        DefaultLogFileMaxSize,
		_oldLogToZip:        false,
		_isFullTextStaining: false,
		_lock:               sync.Mutex{},
		_tempContent:        make([]byte, 0, DefaultLogFileMaxSize+10),
	}
	_ = rwheel.AddTickerFunc(fmt.Sprint("gitee.com/ruige_fun/util/rlog/autoCreateLogFile/", atomic.AddInt64(&uniqueID, 1)), l.autoCreateLogFile, nil, time.Second)
	return l
}

type _logger struct {
	_runtimeCaller      int       //日志
	_consoleWriter      io.Writer //控制台输出
	_file               *os.File  //日志文件输出
	_filepath           string    //日志文件地址
	_consoleLevel       Level     //控制台输出等级
	_fileLevel          Level     //文件输出等级
	_curFileSize        int64     //当前日志文件大小
	_maxFileSize        int64     //最大日志文件大小
	_oldLogToZip        bool      //旧日志文件是否压缩成zip文件
	_isFullTextStaining bool      //是否全文着色输出，false仅tag着色。

	_lock                 sync.Mutex //锁
	_tempContent          []byte     //缓存日志文件
	_autoDeleteOldLogFile uint       //自动删除多少天以前的日志，0则不删除
}

func (l *_logger) autoCreateLogFile(funcKey string, ctx *context.Context) {
	if l == nil || l._filepath == "" {
		return
	}
	l._lock.Lock()
	if len(l._tempContent) > 0 {
		size, _ := l._file.Write(l._tempContent)
		l._curFileSize += int64(size)
		atomic.AddInt64(&waitWriteLogCount, int64(-len(l._tempContent)))
		l._tempContent = make([]byte, 0, DefaultLogFileMaxSize+10)
	}
	if l._curFileSize >= l._maxFileSize {
		atomic.AddInt64(&waitWriteLogCount, 1)
		if l._file != nil {
			_ = l._file.Close()
			l._file = nil

			var err error
			if l._oldLogToZip {
				err = zipSaveAs(l._filepath, fmt.Sprint(l._filepath, ".", time.Now().Format("20060102_150405Z0700"), ".zip"), true)
			} else {
				err = os.Rename(l._filepath, fmt.Sprint(l._filepath, ".", time.Now().Format("20060102_150405Z0700"), path.Ext(l._filepath)))
			}
			if err == nil {
				file, err := os.OpenFile(l._filepath, os.O_CREATE|os.O_WRONLY, LogFileModel)
				if err == nil {
					l._file = file
					l._curFileSize = 0
					fmt.Println(aurora.Green(fmt.Sprint("创建新的日志文件成功：", l._file.Name())))
				} else {
					fmt.Println(aurora.Red(fmt.Sprint("创建新的日志文件失败：", err)))
				}
			} else {
				fmt.Println(aurora.Red(fmt.Sprint("重命名旧日志文件失败：", err)))
			}
		} else if l._filepath != "" {
			l.initCreateFile()
		}
		if l._autoDeleteOldLogFile > 0 {
			//自动删除旧日志文件
			logDir := path.Dir(l._file.Name())
			allList, _ := os.ReadDir(logDir)
			oldTimeLimit := time.Now().Unix() - int64(l._autoDeleteOldLogFile*86400)
			for _, d := range allList {
				if d.IsDir() {
					continue
				}
				base := path.Base(l._file.Name())
				if strings.HasPrefix(d.Name(), base) && d.Name() != base {
					if info, err := d.Info(); err == nil {
						if info.ModTime().Unix() < oldTimeLimit {
							fmt.Println(aurora.Red(fmt.Sprint("自动清理旧日志文件：", os.Remove(path.Join(logDir, info.Name())))))
						}
					}
				}
			}
		}
		atomic.AddInt64(&waitWriteLogCount, -1)
	}
	l._lock.Unlock()
}

func (l *_logger) initCreateFile() {
	if l._file != nil && l._filepath == "" {
		return
	}
	atomic.AddInt64(&waitWriteLogCount, 1)
	defer atomic.AddInt64(&waitWriteLogCount, -1)
	fmt.Println("初始化创建日志文件：", l._filepath)
	l._lock.Lock()
	defer l._lock.Unlock()
	_ = os.MkdirAll(path.Dir(strings.ReplaceAll(l._filepath, `\`, "/")), LogFileModel)
	stat, err := os.Stat(l._filepath)
	if err != nil {
		l._curFileSize = 0
		if strings.Contains(err.Error(), "cannot find") ||
			strings.Contains(err.Error(), "no such") {
			//文件不存在
			file, err := os.OpenFile(l._filepath, os.O_WRONLY|os.O_CREATE, LogFileModel)
			if err != nil {
				fmt.Println(aurora.Red(fmt.Sprint("创建新的日志文件失败：", err)))
				return
			}
			l._file = file
		} else {
			fmt.Println(aurora.Red(fmt.Sprint("读取新的日志文件失败：", err)))
		}
	} else {
		file, err := os.OpenFile(l._filepath, os.O_WRONLY|os.O_APPEND, LogFileModel)
		if err != nil {
			fmt.Println(aurora.Red(fmt.Sprint("创建新的日志文件失败：", err)))
		}
		l._file = file
		l._curFileSize = stat.Size()
	}
}

func (l *_logger) prefix() string {
	now := time.Now()
	year, month, day := now.Date()
	hour, min, sec := now.Clock()
	mill := now.UnixMilli() % 1000
	_, file, line, _ := runtime.Caller(l._runtimeCaller)
	if projectDirLength == 0 || projectDirLength > len(file) {
		return fmt.Sprintf("%4d/%02d/%02d·%02d:%02d:%02d.%03d %v:%v ：", year, int(month), day, hour, min, sec, mill, path.Base(file), line)
	} else {
		return fmt.Sprintf("%4d/%02d/%02d·%02d:%02d:%02d.%03d %v:%v ：", year, int(month), day, hour, min, sec, mill, file[projectDirLength:], line)
	}
}

func (l *_logger) writeFile(content []byte) {
	l._lock.Lock()
	atomic.AddInt64(&waitWriteLogCount, int64(len(content)))
	l._tempContent = append(l._tempContent, content...)
	l._lock.Unlock()
}

func (l *_logger) Print(a ...any) {
	content := []byte(tagPrint + l.prefix() + fmt.Sprint(a...) + "\r\n")
	if l._fileLevel < LevelDisable && l._file != nil {
		go l.writeFile(content)
	}
	if l._consoleLevel < LevelDisable && l._consoleWriter != nil {
		_, _ = l._consoleWriter.Write(content)
	}
}

func (l *_logger) Println(a ...any) {
	content := []byte(tagPrint + l.prefix() + fmt.Sprintln(a...))
	if l._fileLevel < LevelDisable && l._file != nil {
		go l.writeFile(content)
	}
	if l._consoleLevel < LevelDisable && l._consoleWriter != nil {
		_, _ = l._consoleWriter.Write(content)
	}
}

func (l *_logger) Printf(format string, a ...any) {
	content := []byte(tagPrint + l.prefix() + fmt.Sprintf(format, a...) + "\r\n")
	if l._fileLevel < LevelDisable && l._file != nil {
		go l.writeFile(content)
	}
	if l._consoleLevel < LevelDisable && l._consoleWriter != nil {
		_, _ = l._consoleWriter.Write(content)
	}
}

func (l *_logger) Debug(a ...any) {
	content := l.prefix() + fmt.Sprintln(a...)
	if l._fileLevel <= LevelDebug && l._file != nil {
		go l.writeFile([]byte(tagDebug + content))
	}
	if l._consoleLevel <= LevelDebug && l._consoleWriter != nil {
		if l._isFullTextStaining {
			_, _ = l._consoleWriter.Write([]byte(aurora.Blue(tagDebug + content).String()))
		} else {
			_, _ = l._consoleWriter.Write([]byte(aurora.Blue(tagDebug).String() + content))
		}
	}
}

func (l *_logger) Info(a ...any) {
	content := l.prefix() + fmt.Sprintln(a...)
	if l._fileLevel <= LevelInfo && l._file != nil {
		go l.writeFile([]byte(tagInfo + content))
	}
	if l._consoleLevel <= LevelInfo && l._consoleWriter != nil {
		if l._isFullTextStaining {
			_, _ = l._consoleWriter.Write([]byte(aurora.Cyan(tagInfo + content).String()))
		} else {
			_, _ = l._consoleWriter.Write([]byte(aurora.Cyan(tagInfo).String() + content))
		}
	}
}

func (l *_logger) Success(a ...any) {
	content := l.prefix() + fmt.Sprintln(a...)
	if l._fileLevel <= LevelSuccess && l._file != nil {
		go l.writeFile([]byte(tagSuccess + content))
	}
	if l._consoleLevel <= LevelSuccess && l._consoleWriter != nil {
		if l._isFullTextStaining {
			_, _ = l._consoleWriter.Write([]byte(aurora.Green(tagSuccess + content).String()))
		} else {
			_, _ = l._consoleWriter.Write([]byte(aurora.Green(tagSuccess).String() + content))
		}
	}
}

func (l *_logger) Warn(a ...any) {
	content := l.prefix() + fmt.Sprintln(a...)
	if l._fileLevel <= LevelWarn && l._file != nil {
		go l.writeFile([]byte(tagWarn + content))
	}
	if l._consoleLevel <= LevelWarn && l._consoleWriter != nil {
		if l._isFullTextStaining {
			_, _ = l._consoleWriter.Write([]byte(aurora.Yellow(tagWarn + content).String()))
		} else {
			_, _ = l._consoleWriter.Write([]byte(aurora.Yellow(tagWarn).String() + content))
		}
	}
}

func (l *_logger) Error(a ...any) {
	content := l.prefix() + fmt.Sprintln(a...)
	if l._fileLevel <= LevelError && l._file != nil {
		go l.writeFile([]byte(tagError + content))
	}
	if l._consoleLevel <= LevelError && l._consoleWriter != nil {
		if l._isFullTextStaining {
			_, _ = l._consoleWriter.Write([]byte(aurora.Red(tagError + content).String()))
		} else {
			_, _ = l._consoleWriter.Write([]byte(aurora.Red(tagError).String() + content))
		}
	}
}

func (l *_logger) Panic(a ...any) {
	content := l.prefix() + fmt.Sprintln(a...)
	if l._fileLevel <= LevelPanic && l._file != nil {
		go l.writeFile([]byte(tagPanic + content))
	}
	if l._consoleLevel <= LevelPanic && l._consoleWriter != nil {
		if l._isFullTextStaining {
			_, _ = l._consoleWriter.Write([]byte(aurora.Magenta(tagPanic + content).String()))
		} else {
			_, _ = l._consoleWriter.Write([]byte(aurora.Magenta(tagPanic).String() + content))
		}
	}
}

func (l *_logger) ExitError(a ...any) {
	content := l.prefix() + fmt.Sprintln(a...)
	if l._file != nil {
		go l.writeFile([]byte(tagExit + content))
	}
	if l._consoleWriter != nil {
		if l._isFullTextStaining {
			_, _ = l._consoleWriter.Write([]byte(aurora.BrightMagenta(tagExit + content).String()))
		} else {
			_, _ = l._consoleWriter.Write([]byte(aurora.BrightMagenta(tagExit).String() + content))
		}
	}
	WaitFinish()
	os.Exit(1)
}

func (l *_logger) ExitErrorAndSleep3(a ...any) {
	content := l.prefix() + fmt.Sprintln(a...)
	if l._file != nil {
		go l.writeFile([]byte(tagExit + content))
	}
	if l._consoleWriter != nil {
		if l._isFullTextStaining {
			_, _ = l._consoleWriter.Write([]byte(aurora.BrightMagenta(tagExit + content).String()))
		} else {
			_, _ = l._consoleWriter.Write([]byte(aurora.BrightMagenta(tagExit).String() + content))
		}
	}
	WaitFinish()
	time.Sleep(time.Second * 3)
	os.Exit(1)
}

func (l *_logger) PrintlnConsoleMust(a ...any) {
	content := []byte(tagMust + l.prefix() + fmt.Sprintln(a...))
	if l._fileLevel < LevelDisable && l._file != nil {
		go l.writeFile(content)
	}
	if l._consoleWriter != nil {
		_, _ = l._consoleWriter.Write(content)
	}
}

func (l *_logger) PrintlnFileMust(a ...any) {
	content := []byte(tagMust + l.prefix() + fmt.Sprintln(a...))
	if l._file != nil {
		go l.writeFile(content)
	}
	if l._consoleLevel < LevelDisable && l._consoleWriter != nil {
		_, _ = l._consoleWriter.Write(content)
	}
}

func (l *_logger) PrintlnConsoleMustOnly(a ...any) {
	content := []byte(tagMustOnly + l.prefix() + fmt.Sprintln(a...))
	if l._consoleWriter != nil {
		_, _ = l._consoleWriter.Write(content)
	}
}

func (l *_logger) PrintlnFileMustOnly(a ...any) {
	content := []byte(tagMustOnly + l.prefix() + fmt.Sprintln(a...))
	if l._file != nil {
		go l.writeFile(content)
	}
}

func (l *_logger) SetLogFilePath(filepath string, oldLogToZip bool) {
	l._oldLogToZip = oldLogToZip
	l._filepath = strings.ReplaceAll(filepath, `\`, `/`) //替换成统一的路径斜线
	l.initCreateFile()
}

func (l *_logger) SetConsoleLevel(level Level) {
	l._consoleLevel = level
}

func (l *_logger) SetFileLevel(level Level) {
	l._fileLevel = level
}

func (l *_logger) SetFullTextStaining(full bool) {
	l._isFullTextStaining = full
}

func (l *_logger) SetAutoDeleteOldLogFile(day uint) {
	l._autoDeleteOldLogFile = day
}

func (l *_logger) SetLogFileMaxSize(mb uint) {
	if mb <= 0 {
		mb = 10
	}
	l._maxFileSize = 1024 * 1024 * int64(mb)
}

func (l *_logger) setRuntimeCaller(skip int) {
	l._runtimeCaller = skip
}
